package com.demo.nien;

import cn.hutool.core.date.DateUtil;
import com.demo.util.Print;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.StampedLock;

/**
 * @author lkz
 * @ClassName: StampedLockTest
 * @description: 邮戳锁
 * @date 2022/6/20 17:00
 */
public class StampedLockTest {

    //创建一个 Map，代表共享数据
    final static Map<String, String> MAP = new HashMap<String, String>();
    //创建一个印戳锁
    final static StampedLock STAMPED_LOCK = new StampedLock();
    //对共享数据的写操作
    public static Object put(String key, String value)
    {
        long stamp = STAMPED_LOCK.writeLock(); //尝试获取写锁的印戳
        try
        {
            Print.tco(DateUtil.now() + " 抢占了 WRITE_LOCK，开始执行 write 操作");
            Thread.sleep(1000);
            String put = MAP.put(key, value);
            return put;
        } catch (Exception e)
        {
            e.printStackTrace();
        } finally
        {
            Print.tco(DateUtil.now() + " 释放了 WRITE_LOCK");
            STAMPED_LOCK.unlockWrite(stamp); //释放写锁
        }
        return null;
    }
    //对共享数据的悲观读操作
    public static Object pessimisticRead(String key)
    {
        Print.tco(DateUtil.now() + "LOCK 进入过写模式，只能悲观读");
        //进入了写锁模式，只能获取悲观读锁
        long stamp = STAMPED_LOCK.readLock(); //尝试获取读锁的印戳
        try
        {
            //成功获取到读锁，并重新获取最新的变量值
            Print.tco(DateUtil.now() + " 抢占了 READ_LOCK");
            String value = MAP.get(key);
            return value;
        } finally
        {
            Print.tco(DateUtil.now() + " 释放了 READ_LOCK");
            STAMPED_LOCK.unlockRead(stamp); //释放读锁
        }
    }

    //对共享数据的乐观读操作
    public static Object optimisticRead(String key) throws InterruptedException {
        String value = null;
        //尝试进行乐观读
        long stamp = STAMPED_LOCK.tryOptimisticRead();
        if (0 != stamp)
        {
            Print.tco(DateUtil.now() + "乐观读的印戳值，获取成功");
            Thread.sleep(1000); //模拟耗费时间 1 秒
            value = MAP.get(key);
        } else // 0 == stamp 表示当前为写锁模式
        {
            Print.tco(DateUtil.now() + "乐观读的印戳值，获取失败");
            //LOCK 已经进入写模式，使用悲观读方法
            return pessimisticRead(key);
        }
        //乐观读操作已经间隔了一段时间，期间可能发生写入
        //所以，需要验证乐观读的印戳值是否有效，即判断 LOCK 是否进入过写模式
        if (!STAMPED_LOCK.validate(stamp))
        {
            //乐观读的印戳值无效，表明写锁被占用过
            Print.tco(DateUtil.now() + " 乐观读的印戳值，已经过期");
            //写锁已经被抢占，进入了写锁模式，只能通过悲观读锁，再一次读取最新值
            return pessimisticRead(key);
        } else
        {
            //乐观读的印戳值有效，表明写锁没有被占用过
            //不用加悲观读锁而直接读，减少了读锁的开销
            Print.tco(DateUtil.now() + " 乐观读的印戳值，没有过期");
            return value;
        }
    }
    public static void main(String[] args) throws InterruptedException
    {
        //创建 Runnable 可执行实例
        Runnable writeTarget = () -> put("key", "value");
        Runnable readTarget = () -> {
            try {
                optimisticRead("key");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };
        //创建 1 条写线程，并启动
        new Thread(writeTarget, "写线程").start();
        //创建 1 条读线程
        new Thread(readTarget, "读线程").start();
    }
}

